/*************************************************************************
	> File Name: tcpserver.cpp
	> Author: xuwenlong
	> Mail: myxuan475@126.com
	> Created Time: 2018年01月22日 星期一 15时27分39秒
 ************************************************************************/
#include "downlink_connect.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>

#include <utils/logger.h>
#include "serial/serial.h"
#include "tcp/htsocket.h"

namespace downlink {
DownlinkConnect::DownlinkConnect(const dbms::DeviceChannel_t &dc)
	: timeout_(dc.timeout())
{
}

DownlinkConnect::~DownlinkConnect() {}

std::shared_ptr<DownlinkConnect> DownlinkConnect::ConnectFactory(
	const dbms::DeviceChannel_t &dc)
{
	if (dc.ctype() == dbms::CT_COM) {
		return std::make_shared<SerialConnect>(dc);
	} else if (dc.ctype() == dbms::CT_TCP) {
		return std::make_shared<SocketConnect>(dc);
	} else {
		// ASSERT(dc.ctype() == dbms::CT_COM || dc.ctype() == dbms::CT_TCP);
		return nullptr;
	}
}

void DownlinkConnect::OnReceive(const std::function<void(uint8_t *, int)> &func)
{
	onReceive_ = func;
}

void DownlinkConnect::OnError(std::function<void(int32_t)> onError)
{
	onError_ = onError;
}

bool DownlinkConnect::Send(uv_loop_t *uvLoop, const uint8_t cmd[], int len)
{
	std::lock_guard<std::mutex> lockChannel(chnnelMutex_);
	isFinished_ = false;
	HTELINK_LOG_CMD(std::string(Name()).c_str(), cmd, len);
	if (!ReConnect()) {
		HTELINK_LOG_ERR("connect failed, %s", Name().c_str());
		isFinished_ = true;
		return false;
	}
#if DC_USE_THREAD == 0
	int fl = fcntl(Handle(), F_GETFL, 0);
	fcntl(Handle(), F_SETFL, fl | O_NONBLOCK);

	HTELINK_LOG_INFO("blocking? %d", fcntl(Handle(), F_GETFL, 0) & O_NONBLOCK);
	int ret = 0;
	if ((ret = uv_poll_init(uvLoop, &poll_, Handle())) != 0) {
		isFinished_ = true;
		HTELINK_LOG_ERR(
			"bind uv handle %d failed, %s", Handle(), strerror(errno));
		return false;
	}
	poll_.data = this;
	ret = uv_poll_start(
		&poll_, UV_READABLE, [](uv_poll_t *handle, int status, int events) {
			DownlinkConnect *downlink =
				static_cast<DownlinkConnect *>(handle->data);
			ASSERT(downlink != nullptr);
			downlink->OnEvents(status, events);
		});
	if (ret != 0) {
		HTELINK_LOG_ERR("uv poll start failed, %d", ret);
		isFinished_ = true;
		return false;
	}
#else
	std::thread threadRead([&]() {
		while (!isFinished_) {
			if (IsAvailable()) {
				OnEvents(0, UV_READABLE);
			}
			std::this_thread::sleep_for(std::chrono::milliseconds(5));
		}
	});
	threadRead.detach();
#endif

	if (!Send(cmd, len)) {
		HTELINK_LOG_ERR("send failed, %s", Name().c_str());
		OnStop();
		return false;
	}
	return true;
}

void DownlinkConnect::OnEvents(int32_t status, int32_t events)
{
	if (status < 0) {
		HTELINK_LOG_ERR(
			"error occured, %d, %d, %s", events, status, strerror(errno));
		if (onError_) {
			onError_(errno);
		}
		return;
	}

	if ((events & UV_READABLE)) {
		uint8_t buffer[256];
		int32_t length = 0;
		// length = read(Handle(), buffer, sizeof(buffer));
		length = Recv(buffer, sizeof(buffer));
		HTELINK_LOG_DEBUG("recv length: %d", length);
		if (onReceive_ && length > 0) {
			HTELINK_LOG_CMD(std::string(Name()).c_str(), buffer, length);
			onReceive_(buffer, length);
		}
	} else {
		HTELINK_LOG_DEBUG("events %d not handle, %s", events, strerror(errno));
		return;
	}
}

void DownlinkConnect::OnStop()
{
#if DC_USE_THREAD == 0
	if (poll_.data == this) {
		uv_poll_stop(&poll_);
		poll_.data = nullptr;
	}
#endif
	CloseConnect();
	isFinished_ = true;
	if (onError_) {
		onError_(0);
	}
}

void DownlinkConnect::Stop()
{
	OnStop();
	onError_ = nullptr;
	onReceive_ = nullptr;
	isFinished_ = true;
}
bool DownlinkConnect::IsFinished() const { return isFinished_; }
} // namespace downlink